﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Dubbo.Rpc
{
    public abstract class AbstractProxyProtocol : AbstractProtocol
    {
        private readonly ConcurrentStack<Type> rpcExceptions = new ConcurrentStack<Type>();

        [Import]
        public IProxyFactory ProxyFactory { get; set; }

        public AbstractProxyProtocol()
        {
        }

        public AbstractProxyProtocol(Type[] exceptions)
        {
            foreach (var exception in exceptions)
            {
                AddRpcException(exception);
            }
        }

        public void AddRpcException(Type exception)
        {
            this.rpcExceptions.Push(exception);
        }


        public override IExporter<T> Export<T>(IInvoker<T> invoker)
        {
            string uri = serviceKey(invoker.Url);
            IExporter value;
            if (exporterDict.TryGetValue(uri, out value))
            {
                return (IExporter<T>)value;
            }
            Action action = DoExport<T>(ProxyFactory.GetProxy(invoker), invoker.Url);

            IExporter<T> exporter = new DefaultExporter<T>(invoker, uri, exporterDict, action);
            exporterDict.AddOrUpdate(uri, exporter, (k, v) => exporter);
            return exporter;
        }

        class DefaultExporter<T> : AbstractExporter<T>
        {
            private readonly string uri;
            private readonly ConcurrentDictionary<string, IExporter> exporterDict;
            private readonly Action action;

            public DefaultExporter(IInvoker<T> invoker, string uri,
                ConcurrentDictionary<string, IExporter> exporterDict, Action action)
                : base(invoker)
            {
                this.uri = uri;
                this.exporterDict = exporterDict;
                this.action = action;
            }

            public override void Unexport()
            {
                base.Unexport();
                IExporter value;
                exporterDict.TryRemove(uri, out value);
                if (action != null)
                {
                    try
                    {
                        action.Invoke();
                    }
                    catch (Exception ex)
                    {
                        //logger.warn(t.getMessage(), t);
                    }
                }
            }

        }

        public override IInvoker<T> Refer<T>(URL url)
        {
            IInvoker<T> tagert = ProxyFactory.GetInvoker(DoRefer<T>(url), url);
            IInvoker<T> invoker = new DefaultInvoker<T>(tagert, url);
            invokers.Add(invoker);
            return invoker;
        }

        class DefaultInvoker<T> : AbstractInvoker<T>
        {
            private IInvoker<T> tagert;
            private URL url;

            public DefaultInvoker(IInvoker<T> tagert, URL url)
                : base(url)
            {
                this.tagert = tagert;
                this.url = url;
            }

            protected override IResult DoInvoke(IInvocation<T> invocation)
            {
                try
                {
                    IResult result = tagert.Invoke(invocation);
                    Exception e = result.getException();
                    if (e != null)
                    {
                        //foreach (Type rpcException in rpcExceptions)
                        //{
                        //    if (rpcException.isAssignableFrom(e.getClass()))
                        //    {
                        //        throw getRpcException(type, url, invocation, e);
                        //    }
                        //}
                    }
                    return result;
                }
                //    catch (RpcException e) {
                //    if (e.getCode() == RpcException.UNKNOWN_EXCEPTION)
                //    {
                //        e.setCode(getErrorCode(e.getCause()));
                //    }
                //    throw e;
                //}
                catch (Exception e)
                {
                    throw CreateRpcException<T>(url, invocation, e);
                }
            }
        }

        protected static RpcException CreateRpcException<T>(URL url, IInvocation invocation, Exception e)
        {
            RpcException re = new RpcException("Failed to invoke remote service: " + typeof(T) + ", method: "
                    + invocation.MethodName + ", cause: " + e.Message, e);
            //re.setCode(getErrorCode(e));
            return re;
        }

        //protected int getErrorCode(Throwable e)
        //{
        //    return RpcException.UNKNOWN_EXCEPTION;
        //}

        protected abstract Action DoExport<T>(T impl, URL url);

        protected abstract T DoRefer<T>(URL url);
    }
}
